﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.Drawing;
using Vortex.SceneFramework.CSS;
using Vortex.Resources;
using Vortex.IO;
using Vortex.Debugging;
using Vortex.Utils;
using System.Reflection;
using Vortex.Utils.Reflection;
using Vortex.SceneFramework.Gui;

namespace Vortex.SceneFramework {

	/// <summary>
	/// Domain node is base unit of domain framework. It represent resource context and 
	/// </summary>
	public class DomainNode : ViewNode, IDisposable {
		private string _name;
		private string _baseDir;
		//domain objects
		private SceneResources _resources;
		private TimeLine _timeLine = new TimeLine();
		private ScenePropertyResolver _propertyResolver;
		private StyleSheet _styleSheet;

		/// <summary>
		/// Initializes a new instance of the <see cref="BaseScene"/> class. Domain name will be made from short type name (removing domain word)
		/// </summary>
		protected DomainNode() {
			//invoke domain initialization routine
			InitializeScene(
				MakeSceneName()
			);
		}

		/// <summary>
		/// Invoked when domain is activated
		/// </summary>
		protected internal virtual void Activate() { }

		/// <summary>
		/// Invoked when domain is deactivated
		/// </summary>
		protected internal virtual void Deactivate() { }

		/// <summary>
		/// Invoked before all of domain components are created and resource are loaded. It is early load routine. May be useful for creating domain components etc.
		/// </summary>
		protected virtual void BeforeLoad() { }

		/// <summary>
		/// Invoked when all of domain components are created and resources/styles/layouts are loaded. Place custom resource loading here, add event handlers to elements etc... It is late load routine.
		/// </summary>
		protected virtual void Load(IResourceCollection collection) { }

		/// <summary>
		/// Invoked when domain is destructed to free all of custom resources.
		/// </summary>
		protected virtual void Unload() { }

		/// <summary>
		/// Gets the default name of the domain.
		/// </summary>
		/// <returns></returns>
		private string MakeSceneName() {
			return GetType().Name.Replace("Scene", "");
		}

		/// <summary>
		/// Gets the property resolver for this domain.
		/// </summary>
		/// <returns>An instance of <see cref="IPropertyResolver"/> which resolves string values in context of this domain</returns>
		public IPropertyResolver GetPropertyResolver() {
			return _propertyResolver;
		}

		/// <summary>
		/// Returns this domain  reference loopback.
		/// </summary>
		/// <value>This domain reference loopback.</value>
		public override DomainNode Domain {
			get { return this;}
		}

		/// <summary>
		/// Initializes the domain.
		/// </summary>
		/// <param name="sceneName">Name of the domain.</param>
		private void InitializeScene(string sceneName) {
			if (null == sceneName) throw new ArgumentNullException("sceneName");

			//set default properties:
			BackgroundColor = ColorU.Invisible;
			BackgroundSprite = null;
			BackgroundSpriteTint = ColorU.Blank;

			using (Log.ImportantUp("Initializing scene '{0}'", sceneName)) {
				PerfTime begin = PerfTime.Now;

				_name = sceneName;
				_baseDir = @"{scenes}\" + sceneName;

				//0. Call before load
				BeforeLoad();
				
				//1. Load all of resources from definition into domain
				_resources = new SceneResources(this);

				//2. Create property resolver instance
				_propertyResolver = new ScenePropertyResolver(this);

				//3. Create domain stylesheet which used domain property resolver
				_styleSheet = new StyleSheet(_propertyResolver);

				//4. Check and process layout definition of domain
				ResourceFileInfo layoutFile = new ResourceFileInfo(string.Format(@"{{scenes}}\{0}\{0}.layout.xml", Name));
				if (layoutFile.Exists) {
					//create loader and load domain layout definition
					SceneLayoutLoader layoutLoader = new SceneLayoutLoader(this);
					layoutLoader.Load(layoutFile.FileName);					
				}

				//5. Process domain references
				ResolveInstanceReferences(this, true);

				//6. Scan for domain components among the elements
				InitAllOfSceneComponents(this);

				PerfTimeSpan sceneInitTime = PerfTime.Now - begin;
				Log.Important("Scene init time: {0:F1} ms", sceneInitTime.Milliseconds);

				//6. Invoke domain load after all of stuff is done
				Load(Resources);
			}
		}

		/// <summary>
		/// Initializes all of domain components among the domain elements.
		/// </summary>
		/// <param name="element">The element to start with.</param>
		private void InitAllOfSceneComponents(Node element) {
			foreach (Node child in element.Children) {
				ISceneComponent component = child as ISceneComponent;
				if (null != component) {
					//resolve field/properties referred to elements
					ResolveInstanceReferences(component, true);

					//invoke ComponentLoad after all of fields/properties were resolved
					component.ComponentLoad(this.Resources);
				}

				//if element has children, lets process them too
				if (child.HasChildren) {
					InitAllOfSceneComponents(child);
				}
			}
		}

		/// <summary>
		/// Destroys all of domain components.
		/// </summary>
		/// <param name="element">The element to start with.</param>
		private void DestroyAllOfSceneComponents(Node element) {
			foreach (Node child in element.Children) {
				ISceneComponent component = child as ISceneComponent;
				if (null != component) {
					component.ComponentUnload();
				}

				//if element has children, lets process them too
				if (child.HasChildren) {
					DestroyAllOfSceneComponents(child);
				}
			}
		}

		/// <summary>
		/// Resolves all of the instance references.
		/// </summary>
		/// <param name="instance">The instance which properties/fields should be resolved.</param>
		/// <param name="recursive">if set to <c>true</c> then recursive scan will be aplied.</param>
		private void ResolveInstanceReferences(object instance, bool recursive) {
			BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
			Type elementRefType = typeof(ElementRefAttribute);
			Type resourceRefType = typeof(ResourceRefAttribute);
			Type sceneComponentType = typeof(SceneComponentAttribute);
			//type of instances
			Type instanceType = instance.GetType();

			//process all of fields inside of instance
			FieldInfo[] fields = instanceType.GetFields(bindingFlags);
			foreach (FieldInfo field in fields) {
				//check resolve element reference
				object[] elementRef = field.GetCustomAttributes(elementRefType, false);
				if (elementRef.Length > 0) {
					ResolveElementReference(new FieldValueProxy(field, instance), (ElementRefAttribute)elementRef[0]);
				}
				//check resolve resource reference
				object[] resourceRef = field.GetCustomAttributes(resourceRefType, false);
				if (resourceRef.Length > 0) {
					ResolveResourceReference(new FieldValueProxy(field, instance), (ResourceRefAttribute)resourceRef[0]);
				}

				//check recursive
				if (recursive) {
					//check resolve resource reference
					object[] sceneComponent = field.GetCustomAttributes(sceneComponentType, false);
					if (sceneComponent.Length > 0) {
						object value = new FieldValueProxy(field, instance).Value;
						if (null != value) {
							ResolveInstanceReferences(value, true);
						}
					}
				}
			}

			//process all of properties of insrance
			PropertyInfo[] properties = instanceType.GetProperties(bindingFlags);
			foreach (PropertyInfo property in properties) {
				//check resolve element reference
				object[] elementRef = property.GetCustomAttributes(elementRefType, false);
				if (elementRef.Length > 0) {
					ResolveElementReference(new PropertyValueProxy(property, instance), (ElementRefAttribute)elementRef[0]);
				}
				//check resolve resource reference
				object[] resourceRef = property.GetCustomAttributes(resourceRefType, false);
				if (resourceRef.Length > 0) {
					ResolveResourceReference(new PropertyValueProxy(property, instance), (ResourceRefAttribute)resourceRef[0]);
				}

				//check recursive
				if (recursive) {
					//check resolve resource reference
					object[] sceneComponent = property.GetCustomAttributes(sceneComponentType, false);
					if (sceneComponent.Length > 0) {
						object value = new PropertyValueProxy(property, instance).Value;
						if (null != value) {
							ResolveInstanceReferences(value, true);
						}
					}
				}
			}

		}

		/// <summary>
		/// Determines whether is this object is domain component type.
		/// </summary>
		/// <param name="type">The type to check.</param>
		/// <returns>
		/// 	<c>true</c> if type supports domain component interface; otherwise, <c>false</c>.
		/// </returns>
		private bool IsSceneComponentType(Type type) {
			return typeof(ISceneComponent).IsAssignableFrom(type);
		}

		/// <summary>
		/// Resolves the resource reference.
		/// </summary>
		/// <param name="valueProxy">The value proxy.</param>
		/// <param name="resourceRefAttribute">The resource reference attribute.</param>
		private void ResolveResourceReference(IValueProxy valueProxy, ResourceRefAttribute resourceRefAttribute) {
			string resourceId = resourceRefAttribute.ResourceID ?? valueProxy.Name;
			object resourceObject;
			
			if (_resources.TryGet(resourceId, out resourceObject)) {
				//check type matching before set
				if (valueProxy.ValueType.IsInstanceOfType(resourceObject)) {
					//set value (!)
					valueProxy.Value = resourceObject;
				} else {
					throw new ResourceReferenceResolveException(
						string.Format("Resource of type '{0}' can not be cast to value of type '{1}'", resourceObject.GetType().FullName, valueProxy.ValueType.FullName),
						resourceId
					);
				}
			} else if (resourceRefAttribute.Required) {
				throw new ResourceReferenceResolveException("Resource doesn't exist", resourceId);
			}
		}

		/// <summary>
		/// Resolves the element reference.
		/// </summary>
		/// <param name="valueProxy">The value proxy.</param>
		/// <param name="elementRefAttribute">The element reference attribute.</param>
		private void ResolveElementReference(IValueProxy valueProxy, ElementRefAttribute elementRefAttribute) {
			string elementId = elementRefAttribute.ElementID ?? valueProxy.Name;
			Node element = this.SelectNodeById(elementId);
			//check 
			if (element != null) {
				//check type matching before set
				if (valueProxy.ValueType.IsInstanceOfType(element)) {
					//set value (!)
					valueProxy.Value = element;
				} else {
					throw new ElementReferenceResolveException(
						string.Format("Element of type '{0}' can not be cast to value of type '{1}'", element.GetType().FullName, valueProxy.ValueType.FullName),
						elementId
					);
				}
			} else if (elementRefAttribute.Required) {
				throw new ElementReferenceResolveException("Element doesn't exist", elementId);
			}
		}

		#region IDisposable Members

		/// <summary>
		/// Performs cleaning of domain resources
		/// </summary>
		public void Dispose() {
			//destroy all of domain components
			DestroyAllOfSceneComponents(this);

			//invoke domain unload
			Unload();
			//free domain resource on unload
			if (null != _resources) {
				_resources.Dispose();
			}
		}

		#endregion

		/// <summary>
		/// Gets the name of domain.
		/// </summary>
		/// <value>The name of domain.</value>
		public string Name {
			get { return _name; }

		}

		/// <summary>
		/// Gets the base directory of string. It is ready to use for concatenation with file name, e.g. BaseDir + "filename.ext".
		/// </summary>
		/// <value>The base directory of domain resources/assets.</value>
		public string BaseDir {
			get { return _baseDir; }
		}

		///<summary>Individual domain time-line</summary>
		public TimeLine TimeLine {
			get { return _timeLine; }
		}

		/// <summary>
		/// Gets the style sheet owned by this domain.
		/// </summary>
		/// <value>The style sheet of this domain.</value>
		public override StyleSheet StyleSheet {
			get { return _styleSheet; }
		}

		/// <summary>
		/// Gets the domain resources provider interface.
		/// </summary>
		/// <value>The domain resource provider interface.</value>
		public IResourceCollection Resources {
			get { return _resources; }
		}

		/// <summary>
		/// Gets or sets the background color of domain. If color is ColorU.Invisible then domain doesn't clear background itself, it is default value. 
		/// </summary>
		/// <value>The color of the  domain background.</value>
		public ColorU BackgroundColor {
			get; set;
		}

		/// <summary>
		/// Gets or sets the background sprite which fills domain.
		/// </summary>
		/// <value>The background sprite which fills domain.</value>
		public Sprite BackgroundSprite {
			get; set;
		}

		/// <summary>
		/// Gets or sets the background sprite tint.
		/// </summary>
		/// <value>The background sprite tint.</value>
		public ColorU BackgroundSpriteTint {
			get; set;
		}

		/// <summary>
		/// Updates domain for this frame time change
		/// </summary>
		/// <param name="timeDelta">The time delta in seconds.</param>
		public override void Update(float timeDelta) {
			TimeLine.Update(timeDelta);
			base.Update(timeDelta);
		}

		/// <summary>
		/// Overrides drawing domain content. Domain intercepts background drawing.
		/// </summary>
		/// <param name="canvas">The canvas to use for domain drawing.</param>
		public override void DrawContent(Canvas2D canvas) {
			//clear background of canvas
			if (BackgroundColor != ColorU.Invisible) {
				canvas.Clear(BackgroundColor);
			}
			//fill canvas with sprite
			if (null != BackgroundSprite) {
				canvas.DrawSprite(canvas.Region, BackgroundSprite, BackgroundSpriteTint);
			}

			base.DrawContent(canvas);
		}
	}
}
